Skip to content

Complete #1560 by adding deconstructing foreach#1689

Merged
jskeet merged 2 commits into
dotnet:draft-v8from
Nigel-Ecma:decons-fe
Jun 10, 2026
Merged

Complete #1560 by adding deconstructing foreach#1689
jskeet merged 2 commits into
dotnet:draft-v8from
Nigel-Ecma:decons-fe

Conversation

@Nigel-Ecma

Copy link
Copy Markdown
Contributor

Closes #1602

  • Complete Update “tuple” handling – rebased #1560 by covering foreach_statement using a deconstructor
  • Add sample “Deconstructing foreach”
  • Change subclause title from “await foreach” to “Asynchronous foreach” for consistency with siblings
  • Also cleans up some whitespace

Closes dotnet#1602

- Complete dotnet#1560 by covering *foreach_statement* using
  a *deconstructor*
- Add sample “Deconstructing foreach”
- Change subclause title from “await foreach” to “Asynchronous
  foreach” for consistency with siblings
- Also cleans up some whitespace
@Nigel-Ecma Nigel-Ecma added this to the C# 8.0 milestone May 13, 2026
@Nigel-Ecma Nigel-Ecma self-assigned this May 13, 2026

@BillWagner BillWagner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start. It looks like the specter of discards in a deconstructing assignment has made its appearance again.

Comment thread standard/statements.md
try
{
while (await enumerator.MoveNextAsync())
var enumerator = enumerable.GetAsyncEnumerator();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the only change here is indenting. Correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expansions for the others where all wrapped in an enclosing block and this one was missed/different. So yes, the whole thing was indented and unindented { & } added to wrap it – not that the diff makes that easy to spot!

Comment thread standard/statements.md

#### §deconstructing-foreach Deconstructing foreach

A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of zero or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the changes in 1560, this should be "2 or more":

Suggested change
A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of zero or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.
A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of two or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BillWagner – I had completely forgotten that #1639 introduced targets and counted them rather than assigned named variables, so I just wrote this doing the same as that is what is important – discards are discarded afterall :-)

Your proposed change here however makes it “two or more iteration variables” so more wordsmithing would be required to go the “target” route. I think targets would not fit well here, but then I went with the consensus on §12.23 so that is not too surprising. [Note that #1639 actually contradicts itself, it starts by stating two or more targets are assigned, then later that discard targets are not assigned (“but no value is stored”).]

That discards may be present is covered just below in line 1444. I did consider whether to list the allowed, which would have surfaced discards, or disallowed alternatives, and decided the key issue was that existing variables could not be assigned to so highlighted that by just listed the disallowed variable_reference.

Suggestion: We leave this PR as is on this topic and then you & @jskeet raise an issue to align the text in §12.23 to either use targets (which are not all assigned) in both, neither, or go with variety is the spice of life :-) Mark it pre-C#9 so its not a blocker – what exists here & in §12.23 are not wrong per se.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That suggestion is fine by me.

Comment thread standard/statements.md Outdated
}
```

this follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, 2 or more, and we should add text about how discards are handled:

Suggested change
this follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables:
this follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns two or more initialisation variables:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on line 1442.

Comment thread standard/statements.md

> *Note*: An `await foreach` is not required to dispose of `e` synchronously if an asynchronous dispose mechanism is not available. *end note*

#### §deconstructing-foreach Deconstructing foreach

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran a couple tests, and we need to specify discards in a deconstructing foreach. I think the language should be similar to what was added in #1639.

What's interesting here is that a synchronous and asynchronous foreach don't allow the iteration variable to be a discard. However, in a deconstructing foreach, any number of the iteration variables may be a discard (from 0 to all).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BillWagner – see reply on line 1442

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BillWagner PS: On your “What’s interesting” – are you using “interesting” as (apocryphally?) used in a Chinese curse? ;-) BTW a certain compiler rejects ... is _ but accepts ... is (_) – can that be justified? Maybe. Is it confusing? Probably. We “live in interesting times”.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nigel-Ecma This comment helped me understand the final changes needed for #1688. ... is _ is a discard_pattern, but ... is (_) is a parenthesised_pattern that encloses a discard_pattern. Because the discard_pattern is a subpattern, it's allowed.

@Nigel-Ecma Nigel-Ecma added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Jun 10, 2026
Comment thread standard/statements.md Outdated
Comment thread standard/statements.md Outdated
Comment thread standard/statements.md Outdated
Co-authored-by: Jon Skeet <skeet@pobox.com>
@jskeet
jskeet merged commit 9dc116e into dotnet:draft-v8 Jun 10, 2026
7 checks passed
@Nigel-Ecma
Nigel-Ecma deleted the decons-fe branch June 13, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

meeting: discuss This issue should be discussed at the next TC49-TG2 meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EBNF for the foreach_statement is incorrect. (draft-v8)

3 participants